﻿namespace linkedin_bot_demo.ViewModels
{
	using System;
	using System.ComponentModel;
	using System.Diagnostics;

	/// <summary>
	/// Base class for all ViewModel classes in the application.
	/// It provides support for property change notifications 
	/// and has a DisplayName property.  This class is abstract.
	/// </summary>
	public abstract class ViewModel : INotifyPropertyChanged, IDisposable
	{
		private bool _isDisposed;

		#region Constructor

		protected ViewModel()
		{
#if DEBUG
			++_instanceCounter;
			DisplayName = GetType().Name;
#endif
		}

		#endregion // Constructor

		#region DisplayName

		/// <summary>
		/// Returns the user-friendly name of this object.
		/// Child classes can set this property to a new value,
		/// or override it to determine the value on-demand.
		/// </summary>
		public virtual string DisplayName { get; protected set; }

		#endregion // DisplayName

		#region Debugging Aides

		/// <summary>
		/// Warns the developer if this object does not have
		/// a public property with the specified name. This 
		/// method does not exist in a Release build.
		/// </summary>
		[Conditional("DEBUG")]
		[DebuggerStepThrough]
		public void VerifyPropertyName(string propertyName)
		{
			// Verify that the property name matches a real,  
			// public, instance property on this object.
			if (TypeDescriptor.GetProperties(this)[propertyName] == null)
			{
				string msg = "Invalid property name: " + propertyName;

				if (this.ThrowOnInvalidPropertyName)
					throw new Exception(msg);
				else
					Debug.Fail(msg);
			}
		}

		/// <summary>
		/// Returns whether an exception is thrown, or if a Debug.Fail() is used
		/// when an invalid property name is passed to the VerifyPropertyName method.
		/// The default value is false, but subclasses used by unit tests might 
		/// override this property's getter to return true.
		/// </summary>
		protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

		#endregion // Debugging Aides

		#region INotifyPropertyChanged Members

		/// <summary>
		/// Raised when a property on this object has a new value.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Raises this object's PropertyChanged event.
		/// </summary>
		/// <param name="propertyName">The property that has a new value.</param>
		protected virtual void OnPropertyChanged(string propertyName)
		{
			this.VerifyPropertyName(propertyName);

			PropertyChangedEventHandler handler = this.PropertyChanged;
			if (handler != null)
			{
				var e = new PropertyChangedEventArgs(propertyName);
				handler(this, e);
			}
		}

		#endregion // INotifyPropertyChanged Members

		#region IDisposable Members

		/// <summary>
		/// Invoked when this object is being removed from the application
		/// and will be subject to garbage collection.
		/// </summary>
		public void Dispose()
		{
			if (_isDisposed)
				return;

			lock (this)
			{
				if (!_isDisposed)
				{
					try { this.OnDispose(); }
					finally { _isDisposed = true; }
				}
			}
		}

		[System.Diagnostics.DebuggerStepThrough]
		[System.Diagnostics.DebuggerHidden]
		protected void VerifyDisposed()
		{
			if (_isDisposed)
				throw new ObjectDisposedException(this.GetType().FullName);
		}

		/// <summary>
		/// Child classes can override this method to perform 
		/// clean-up logic, such as removing event handlers.
		/// </summary>
		protected virtual void OnDispose()
		{
			var handler = Disposed;
			if (handler != null)
				handler(this, EventArgs.Empty);
		}

		public event EventHandler Disposed;

#if DEBUG
		private static volatile int _instanceCounter;
		/// <summary>
		/// Useful for ensuring that ViewModel objects are properly garbage collected.
		/// </summary>
		~ViewModel()
		{
			--_instanceCounter;
			string msg = string.Format("{0} ({1}) ({2}) Finalized. Remain {3}", this.GetType().Name, this.DisplayName, this.GetHashCode(), _instanceCounter);
			System.Diagnostics.Debug.WriteLine(msg);
		}
#endif

		#endregion // IDisposable Members

		public override string ToString()
		{
			VerifyDisposed();

			string displayName = DisplayName;
			if (displayName != null)
				return displayName;

			return base.ToString();
		}
	}
}